local c_lava_source = minetest.get_content_id("default:lava_source")
local c_lava_flowing = minetest.get_content_id("default:lava_flowing")

local portal_disconnected_animation_tile = {
    name = "underworld_portal_disconnected_side.png",
    backface_culling = false,
    animation = {
        type = "vertical_frames",
        aspect_w = 16,
        aspect_h = 16,
        length = 0.5,
    }
}
local portal_vm_data = {}
minetest.register_node("underworld:portal_disconnected", {
    description = "Underworld Portal (Disconnected)",
    drawtype = "nodebox",
    node_box = {
        type = "fixed",
        fixed = {
            {-0.499, -0.499, -0.499, 0.499, 0.499, 0.499}, -- Not 0.5, 0.5, ... due to visual glitches
        },
    },
    tiles = {
        "underworld_transparent.png",
        {name = "underworld_portal_disconnected_bottom.png", backface_culling = false},
        portal_disconnected_animation_tile,
        portal_disconnected_animation_tile,
        portal_disconnected_animation_tile,
        portal_disconnected_animation_tile,
    },
    paramtype = "light",
    sunlight_propagates = true,
    walkable = false,
    diggable = false,
    pointable = false,
    buildable_to = false,
    is_ground_content = false,
    drop = "",
    light_source = math.floor(minetest.LIGHT_MAX / 2),
    groups = {attached_node = 1, not_in_creative_inventory = 1},
    
    on_construct = function(pos)
        minetest.get_node_timer(pos):start(0)
    end,
    
    on_timer = function(pos)
        local meta = minetest.get_meta(pos)
        local target_pos = minetest.string_to_pos(meta:get_string("target"))
        if not target_pos then
            local min_search_y, max_search_y
            if pos.y > underworld.depth then
                min_search_y = -31000
                max_search_y = underworld.depth - underworld.barrier_size
            else
                min_search_y = -50
                max_search_y = 200
            end
            
            local search_pos = {x = pos.x + math.random(-40, 40), y = math.random(min_search_y, max_search_y), z = pos.z + math.random(-40, 40)}
            local minp = vector.subtract(search_pos, 40)
            local maxp = vector.add(search_pos, 40)
            
            minetest.emerge_area(
                minp,
                maxp,
                function(_, _, calls_remaining)
                    if calls_remaining == 0 then
                        local target_dist
                        local vm = minetest.get_voxel_manip()
                        local emin, emax = vm:read_from_map(minp, maxp)
                        local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
                        vm:get_data(portal_vm_data)
                        
                        for z = math.max(minp.z, emin.z), math.min(maxp.z, emax.z) do
                            for y = math.max(min_search_y, minp.y, emin.y), math.min(max_search_y, maxp.y, emax.y) do
                                for x = math.max(minp.x, emin.x), math.min(maxp.x, emax.x) do
                                    local cid = portal_vm_data[area:index(x, y, z)]
                                    if cid == minetest.CONTENT_AIR then
                                        -- Check if node belown is walkable and node above is air
                                        local under_cid = portal_vm_data[area:index(x, y - 1, z)]
                                        local under_node_name = minetest.get_name_from_content_id(under_cid)
                                        local above_cid = portal_vm_data[area:index(x, y + 1, z)]
                                        if minetest.registered_nodes[under_node_name].walkable and above_cid == minetest.CONTENT_AIR then
                                            -- Check for surrounding lava
                                            local lava_around = false
                                            for check_z = -5, 5 do
                                                for check_y = -5, 5 do
                                                    for check_x = -5, 5 do
                                                        local check_cid = portal_vm_data[area:index(x + check_x, y + check_y, z + check_z)]
                                                        if check_cid == c_lava_source or check_cid == c_lava_flowing then
                                                            lava_around = true
                                                        end
                                                    end
                                                end
                                            end
                                            if not lava_around then
                                                -- Found suitable spot
                                                local found_pos = {x = x, y = y, z = z}
                                                local found_dist = vector.distance(pos, search_pos)
                                                if not target_pos or found_dist < target_dist then
                                                    target_pos = found_pos
                                                    target_dist = found_dist
                                                end
                                            end
                                        end
                                    end
                                end
                            end
                        end
                        
                        if target_pos then
                            if minetest.get_node(pos).name == "underworld:portal_disconnected" then
                                minetest.set_node(pos, {name = "underworld:portal"})
                                meta:set_string("target", minetest.pos_to_string(target_pos))
                            end
                        else
                            minetest.get_node_timer(pos):start(1) -- Retry search in one second
                        end
                    end
                end
            )
        end
    end,
})

local portal_animation_tile = {
    name = "underworld_portal_side.png",
    backface_culling = false,
    animation = {
        type = "vertical_frames",
        aspect_w = 16,
        aspect_h = 16,
        length = 0.5,
    }
}
minetest.register_node("underworld:portal", {
    description = "Underworld Portal",
    drawtype = "nodebox",
    node_box = {
        type = "fixed",
        fixed = {
            {-0.499, -0.499, -0.499, 0.499, 0.499, 0.499}, -- Not 0.5, 0.5, ... due to visual glitches
        },
    },
    tiles = {
        "underworld_transparent.png",
        {name = "underworld_portal_bottom.png", backface_culling = false},
        portal_animation_tile,
        portal_animation_tile,
        portal_animation_tile,
        portal_animation_tile,
    },
    paramtype = "light",
    sunlight_propagates = true,
    walkable = false,
    diggable = false,
    pointable = false,
    buildable_to = false,
    is_ground_content = false,
    drop = "",
    light_source = math.floor(minetest.LIGHT_MAX / 2),
    groups = {attached_node = 1, not_in_creative_inventory = 1},
})

local function teleport_ps_def(pos)
    return {
        texture = "underworld_portal_particle.png",
        
        amount = 500,
        time = 0.5,
        
        minpos = vector.subtract(pos, 0.5),
        maxpos = vector.add(pos, 0.5),
        minvel = {x = -2, y = -2, z = -2},
        maxvel = {x = 2, y = 2, z = 2},
        minacc = {x = 0, y = 0, z = 0},
        maxacc = {x = 0, y = 0, z = 0},
        minexptime = 1,
        maxexptime = 1,
        minsize = 0.5,
        maxsize = 2,
        
        glow = minetest.LIGHT_MAX,
    }
end
minetest.register_abm({
    label = "Manage portals",
    nodenames = {"underworld:portal", "underworld:portal_disconnected"},
    interval = 1,
    chance = 1,
    action = function(pos, node)
        local texture
        if node.name == "underworld:portal" then
            texture = "underworld_portal_particle.png"
        else
            texture = "underworld_portal_disconnected_particle.png"
        end
        minetest.add_particlespawner({
            texture = texture,
            
            amount = 50,
            time = 2,
            
            minpos = vector.subtract(pos, 0.5),
            maxpos = vector.add(pos, 0.5),
            minvel = {x = 0, y = 0.25, z = 0},
            maxvel = {x = 0, y = 0.75, z = 0},
            minacc = {x = 0, y = 0.25, z = 0},
            maxacc = {x = 0, y = 0.75, z = 0},
            minexptime = 0.5,
            maxexptime = 1,
            minsize = 0.5,
            maxsize = 2,
            minexptime = 0.5,
            maxexptime = 1,
            minsize = 0.5,
            maxsize = 2,
            
            glow = math.floor(minetest.LIGHT_MAX / 2),
        })
        
        if node.name == "underworld:portal" and math.random(1, 4) == 1 then
            local target_pos = minetest.string_to_pos(minetest.get_meta(pos):get_string("target"))
            if target_pos then
                for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
                    if obj:is_player() then
                        -- Force emerge
                        minetest.get_voxel_manip():read_from_map(vector.subtract(target_pos, 5), vector.add(target_pos, 5))
                        minetest.add_particlespawner(teleport_ps_def(vector.add(obj:get_pos(), {x = 0, y = 1, z = 0})))
                        obj:set_pos(target_pos)
                        minetest.add_particlespawner(teleport_ps_def(vector.add(obj:get_pos(), {x = 0, y = 1, z = 0})))
                    end
                end
            end
        end
    end,
})

minetest.register_node("underworld:portal_base", {
    description = "Underworld Portal Base",
    tiles = {
        "default_coal_block.png",
        "default_obsidian.png",
        "default_obsidian.png^(underworld_portal_base_side.png)",
        "default_obsidian.png^(underworld_portal_base_side.png)",
        "default_obsidian.png^(underworld_portal_base_side.png^[transformFY)",
        "default_obsidian.png^(underworld_portal_base_side.png^[transformFY)",
    },
    is_ground_content = false,
    groups = {cracky = 3, level = 2},
    sounds = default.node_sound_stone_defaults(),
    
    on_construct = function(pos)
        local above = vector.add(pos, {x = 0, y = 1, z = 0})
        if minetest.get_node(above).name == "air" then
            minetest.set_node(above, {name = "underworld:portal_disconnected"})
        end
    end,
    
    on_destruct = function(pos)
        local above = vector.add(pos, {x = 0, y = 1, z = 0})
        local above_node_name = minetest.get_node(above).name
        if above_node_name == "underworld:portal_disconnected" or above_node_name == "underworld:portal" then
            minetest.remove_node(above)
        end
    end,
    
    on_rotate = false,
})

minetest.register_craft({
    output = "underworld:portal_base",
    recipe = {
        {"default:coalblock", "default:coalblock", "default:coalblock"},
        {"default:mese",      "default:mese",      "default:mese"},
        {"default:obsidian",  "default:obsidian",  "default:obsidian"},
    },
})